package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.*;
import java.util.function.Function;

import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;
import static com.hmdp.utils.RedisLockUtil.tryLock;
import static com.hmdp.utils.RedisLockUtil.unLock;

/**
 * Redis缓存工具类
 */
@Component
@Slf4j
public class RedisCommontCreateUtils {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public static final String SHOP_CACHE_KEY = "cache:shop:";

    /**
     * 方法1：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置TTL过期时间
     */
    public static void setJsonValue(String key, Object value, Duration duration, StringRedisTemplate stringRedisTemplate) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), duration);
    }

    /**
     * 方法2：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置逻辑过期时间，用于处理缓存击穿问题
     */
    public static void setJsonValueWithExpire(String key, Object value, LocalDateTime expireTime, StringRedisTemplate stringRedisTemplate) {
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(expireTime);
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 方法2：将任意Java对象序列化为json并存储在string类型的key中，并且可以设置逻辑过期时间，用于处理缓存击穿问题
     */
    public static void setJsonValueWithExpire2(String key, Object value, Duration duration, StringRedisTemplate stringRedisTemplate) {
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plus(duration));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value));
    }

    /**
     * 方法3：根据指定的key查询缓存，并反序列化为指定类型，利用缓存空值的方式解决缓存穿透问题<br>
     * 方法4：根据指定的key查询缓存，并反序列化为指定类型，需要利用逻辑过期解决缓存击穿问题
     */
    public static <T> T getRedisValueWithExpire(String key, Class<T> clazz, StringRedisTemplate stringRedisTemplate) {
        try {
            String jsonStr = stringRedisTemplate.opsForValue().get(key);
            if (StrUtil.isBlank(jsonStr)) {
                return null; // 如果值为空，直接返回null
            }

            // 转换为 RedisData，校验是否能转换
            RedisData redisData = JSONUtil.toBean(jsonStr, RedisData.class);
            if (redisData == null) {
                log.error("反序列化RedisData失败，key: {}", key);
                return null; // 如果反序列化失败，返回null
            }

            LocalDateTime expireTime = redisData.getExpireTime();
            if (expireTime.isAfter(LocalDateTime.now())) {
                // 缓存未过期，获取数据
                return JSONUtil.toBean((JSONObject) redisData.getData(), clazz);
            }

            // 如果缓存过期,则返回null
            log.info("缓存过期,请重新查询数据库,重建缓存，key:{},value:{}", key, jsonStr);
            return null;

        } catch (Exception e) {
            log.error("获取Redis缓存时发生异常，key: {}", key, e);
            return null; // 发生异常时返回null
        }
    }


    public static <T> T getJsonValueWithMuteLock(String key, Class<T> clazz, StringRedisTemplate stringRedisTemplate, Duration duration) {
        String jsonStr = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(jsonStr)) {
            //转换为指定类型并返回
            T bean = JSONUtil.toBean(jsonStr, clazz);
            return bean;
        }
        //缓存未命中
        log.info("缓存未命中,请获取互斥锁,查询数据库,进行缓存重建,key:{},duration:{}", key, duration);
        return null;
    }

    public static <T, ID> T queryWithMuteLock(String KeyPrefix, ID id, StringRedisTemplate stringRedisTemplate, Class<T> type, Function<ID, T> dbCallback, Duration duration) {
        // 拼接key
        String key = KeyPrefix + id;

        // 1. 从redis查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            // 缓存命中, 直接返回
            return JSONUtil.toBean(json, type);
        }

        // 缓存未命中, 尝试获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        T result = null;
        try {
            // 尝试获取锁
            if (!tryLock(lockKey, stringRedisTemplate)) {
                // 获取锁失败，休眠50毫秒后递归调用
                Thread.sleep(50);
                return queryWithMuteLock(KeyPrefix, id, stringRedisTemplate, type, dbCallback, duration);
            }

            // 获取锁成功，查询数据库并缓存
            result = dbCallback.apply(id);
            if (result == null) {
                // 缓存空对象, 防止缓存穿透
                stringRedisTemplate.opsForValue().set(key, "", Duration.ofMinutes(3));
            } else {
                // 缓存重建
                stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(result), duration);
            }
        } catch (InterruptedException e) {
            log.error("线程休眠被中断, key: {}", key, e);
            Thread.currentThread().interrupt(); // 恢复中断状态
        } catch (Exception e) {
            log.error("查询数据库或缓存重建时发生异常, key: {}", key, e);
            throw new RuntimeException("查询数据库或缓存重建失败", e);
        } finally {
            // 确保锁被释放
            try {
                unLock(lockKey, stringRedisTemplate);
            } catch (Exception e) {
                log.error("释放锁失败, key: {}", lockKey, e);
            }
        }
        return result;
    }


    public static <T, ID> T queryWithLogicalExpire(String KeyPrefix, ID id, StringRedisTemplate stringRedisTemplate, Class<T> type, Function<ID, T> dbCallback, Duration durationExpire) {
        // 拼接key
        String key = KeyPrefix + id;
        // 1. 从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        // 2. 判断是否存在
        if (StrUtil.isBlank(json)) {
            return null; // 缓存不存在，直接返回null
        }

        // 3. 反序列化为RedisData对象
        RedisData redisData;
        try {
            redisData = JSONUtil.toBean(json, RedisData.class);
        } catch (Exception e) {
            log.error("反序列化RedisData失败, key: {}", key, e);
            return null;
        }

        // 4. 获取缓存数据和过期时间
        T t = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();

        // 5. 判断缓存是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            return t; // 缓存未过期，直接返回数据
        }

        // 6. 缓存已过期，尝试获取锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean tryLock = tryLock(lockKey, stringRedisTemplate);
        if (!tryLock) {
            return t; // 获取锁失败，返回旧数据
        }

        // 7. 获取锁成功，提交异步任务重建缓存
        try {
            // 使用静态线程池，避免频繁创建线程池
            /*new Runnable() {

                @Override
                public void run() {
                    try {
                        T t1 = dbCallback.apply(id);
                        if (t1 == null) {
                            // 缓存空对象
                            setJsonValueWithExpire2(key, "", durationExpire, stringRedisTemplate);
                            log.warn("此店铺不存在, 缓存空对象, key: {}", key);
                        } else {
                            // 重建缓存
                            setJsonValueWithExpire2(key, t1, durationExpire, stringRedisTemplate);
                            log.info("缓存重建成功, key: {}", key);
                        }
                    } catch (Exception e) {
                        log.error("重建缓存失败, key: {}", key, e);
                    } finally {
                        // 确保锁被释放
                        try {
                            unLock(lockKey, stringRedisTemplate);
                        } catch (Exception e) {
                            log.error("释放锁失败, key: {}", lockKey, e);
                        }
                    }
                }
            };*/
            THREAD_POOL.submit(() -> {
                try {
                    T t1 = dbCallback.apply(id);
                    if (t1 == null) {
                        // 缓存空对象
                        setJsonValueWithExpire2(key, "", durationExpire, stringRedisTemplate);
                        log.warn("此店铺不存在, 缓存空对象, key: {}", key);
                    } else {
                        // 重建缓存
                        setJsonValueWithExpire2(key, t1, durationExpire, stringRedisTemplate);
                        log.info("缓存重建成功, key: {}", key);
                    }
                } catch (Exception e) {
                    log.error("重建缓存失败, key: {}", key, e);
                } finally {
                    // 确保锁被释放
                    try {
                        unLock(lockKey, stringRedisTemplate);
                    } catch (Exception e) {
                        log.error("释放锁失败, key: {}", lockKey, e);
                    }
                }
            });
        } catch (Exception e) {
            log.error("提交异步任务失败, key: {}", key, e);
        }

        return t; // 返回旧数据
    }

    // 静态线程池，避免频繁创建线程池
    private static final ThreadPoolExecutor THREAD_POOL = new ThreadPoolExecutor(
            10, 10, 10, TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(100),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
    );


}
